# -*- coding: utf-8 -*-

"""A*寻路算法，可以完成较为智能的寻路算法"""

__authur__ = 'Yu XH'

import heapq
import numpy as np
# from .hex import BoundedHex
from .hex import HexOffset
# from .handle_map import *


class Node(HexOffset):
    def __init__(self, *args, **kwargs):
        super(Node, self).__init__(*args, **kwargs)
        self.parent = None
        self.cost = 0
        self.road = False

    # 估计从当前点到终点的消耗值
    def cost_to(self, dest):
        # return self.distance_to(dest) * basic_time
        dis = self.calc_distance(dest)
        return dis

    # 计算从当前点到其一个临格的实际消耗值
    def cost_neigh_to(self, neigh, cost_data):
        int4_neigh = offset2int4(neigh.row, neigh.col)
        cost_dict = cost_data[self.row][self.col]
        if int4_neigh in list(cost_dict.keys()):
            return cost_dict[int4_neigh]
        else:
            # 表示不存在
            return 100000

    def blocked(self, no_pass_tuple_list=[], pass_tuple_list=[]):
        #不在地图范围
        # if not map.in_map(self.row, self.col):
        #     return True
        # 按规则不能通行
        # if not rule.can_go(bop, self, hexb, map):
        #     return True
        # 在不可通行的列表内
        if (self.row, self.col) in no_pass_tuple_list:
            return True
        # 在可通行的列表内
        if pass_tuple_list:
            if (self.row, self.col) in pass_tuple_list:
                return False
            else:
                return True
        else:
            return False


def mapid_2_matind(hex, map_size):
    """ 从from myAI.hexmap.handle_map中拷贝过来"""
    if isinstance(hex, list):
        hex = np.array(hex)
    matind_rows = (hex // 100) - map_size['row_lim'][0]
    matind_cols = (hex % 100) - map_size['col_lim'][0]
    n_cols = map_size['col_lim'][1] - map_size['col_lim'][0] + 1
    return matind_rows * n_cols + matind_cols


def matind_2_mapid(ind, map_size):
    """ 2020-6-10 实现了局部地图的转换
    把矩阵索引mat_ind转换成mapID
    """
    if isinstance(ind, list):
        ind = np.array(ind)
    n_cols = map_size['col_lim'][1] - map_size['col_lim'][0] + 1
    hex_rows = (ind // n_cols) + map_size['row_lim'][0]
    hex_cols = (ind % n_cols) + map_size['col_lim'][0]
    return hex_rows * 100 + hex_cols


def offset2int4(row, col):
    return row * 100 + col

def int42offset(coord):
    row = coord // 100
    col = coord % 100
    return row, col

def retrace(cur):
    path = [cur, ]
    while cur.parent is not None:
        cur = cur.parent
        path.append(cur)
    path.reverse()
    path_in4_list = [offset2int4(node.row, node.col) for node in path]
    return path[-1].cost, path_in4_list[1:]

def astar(start_int4, end_int4, myAgent, maxsteps=200):
    start_row, start_col = int42offset(start_int4)
    end_row, end_col = int42offset(end_int4)
    heap = list()
    open = set()
    closed = set()
    current = Node(start_row, start_col)
    end = Node(end_row, end_col)
    open.add(current)
    heap.append((0, 0, current))
    # 炮兵射击点，不通过
    artillery_threat = myAgent.agenda.rae_situation.artillery_threat
    neighbors = myAgent.map.get_neighbors(start_int4)
    neighbors_ind = mapid_2_matind(neighbors, myAgent.get_map_size())
    neighbors_threat = artillery_threat[neighbors_ind]
    max_threat, min_threat = np.max(neighbors_threat), np.min(neighbors_threat)
    if max_threat > min_threat and max_threat >= 2:
        no_pass_neighbors = neighbors_ind[neighbors_threat >= max_threat]
        no_pass_locs = list(matind_2_mapid(no_pass_neighbors, myAgent.get_map_size()))
        no_pass_tuple_list = [(loc // 100, loc % 100) for loc in no_pass_locs]
    else:
        no_pass_tuple_list = []
    while maxsteps > 0 and open:
        maxsteps -= 1
        # 将堆中最上层对象取出，并取其第3个元素，即node
        current = heapq.heappop(heap)[2]
        if current == end:
            return retrace(current)

        open.remove(current)
        closed.add(current)
        for node in myAgent.map.get_neighbors(offset2int4(current.row, current.col)):
            row, col = int42offset(node)
            node = Node(row, col)
            if node in closed:
                continue
            # 对于步兵而言，所有格子都可以走
            if node.blocked(no_pass_tuple_list):
                closed.add(node)
                continue

            # 计算经过当前点到当前点的临格点的消耗值
            new_cost = current.cost + current.cost_neigh_to(node, myAgent.my_map['cost_data'])
            if node in open:
                if node.cost > new_cost:
                    node.cost = new_cost
                    node.parent = current
            else:
                node.cost = new_cost
                node.parent = current
                open.add(node)
                # 先比较总消耗值，再比较到终点的估计值，进行排序
                heapq.heappush(heap, (node.cost + node.cost_to(end), node.cost_to(end), node))
    return 0, list()


def gen_path_with_astar(start_int4, end_int4, mode, my_map, no_pass_list=[], maxsteps=200):
    """ 2022-10-30，yuxh
    使用A*算法生成机动路线，支持输入不可经过区域
    :param start_int4: 起始格，4位int编号
    :param end_int4: 目标格，4位int编号
    :param mode: 通行方式, 取值范围[0-车辆机动, 1-车辆行军, 2-步兵机动, 3-空中机动]
    :param my_map: 地图实例，由map.py的Map类实例化得到，一般输入agent.map
    :param no_pass_list: 不可通过格子列表list(int) or list(tuple)，由六角格4位int编号或者(row, col)偏移坐标组成
    :param maxsteps: 最大迭代数量
    :return: 机动路径
    """
    start_row, start_col = int42offset(start_int4)
    end_row, end_col = int42offset(end_int4)
    heap = list()
    open = set()
    closed = set()
    current = Node(start_row, start_col)
    end = Node(end_row, end_col)
    open.add(current)
    heap.append((0, 0, current))
    # 把no_pass_tuple_list中的元素变成元组(row, col)的形式
    no_pass_tuple_list = []
    if no_pass_list:
        for pos in no_pass_list:
            try:
                assert isinstance(pos, int) or isinstance(pos, tuple)
                if isinstance(pos, int):
                    no_pass_tuple_list.append(divmod(pos, 100))
                else:
                    no_pass_tuple_list.append(pos)
            except Exception as e:
                print("参数no_pass_list中元素的类型错误！")

    while maxsteps > 0 and open:
        maxsteps -= 1
        # 将堆中最上层对象取出，并取其第3个元素，即node
        current = heapq.heappop(heap)[2]
        if current == end:
            return retrace(current)

        open.remove(current)
        closed.add(current)
        for node in my_map.get_neighbors(offset2int4(current.row, current.col)):
            row, col = int42offset(node)
            node = Node(row, col)
            if node in closed:
                continue
            if node.blocked(no_pass_tuple_list):  # 在列表中的格子不考虑
                closed.add(node)
                continue

            # 计算经过当前点到当前点的临格点的消耗值
            new_cost = current.cost + current.cost_neigh_to(node, my_map.cost[mode])
            if node in open:
                if node.cost > new_cost:
                    node.cost = new_cost
                    node.parent = current
            else:
                node.cost = new_cost
                node.parent = current
                open.add(node)
                # 先比较总消耗值，再比较到终点的估计值，进行排序
                heapq.heappush(heap, (node.cost + node.cost_to(end), node.cost_to(end), node))
    return 0, list()
